package gazzera.node.core.plugins.trust;

import gazzera.gui.*;
import gazzera.gui.messages.*;
import gazzera.node.core.*;
import gazzera.node.core.messages.*;
import gazzera.node.core.peer.*;
import gazzera.node.core.plugins.*;

import java.util.*;



/**
 * This plugin handles the trusting system
 *
 */
public class TrustPlugin implements Plugin
{
	/**
	 * This is the list of peoples that are actually trusting me
	 */
	private List<Node> peopleTrustingMe = new ArrayList<Node>();

	private TrustNet net = new TrustNet();
	private ICore core = null;
	
	@Override
	public void command(GuiMessage msg) throws Exception
	{
		if (msg instanceof ToggleTrust)
		{
			String hash = ((ToggleTrust) msg).hash;
			toggleTrust(hash, core.getPeoples());
		}
		

	}

	
	
	@Override
	public void dataPumpTerminated(Node node, DataPumpSource src) throws Exception
	{
		

	}

	@Override
	public void finish() throws Exception
	{
		

	}

	@Override
	public void init(ICore core) throws Exception
	{
		this.core = core;

	}

	@Override
	public void message(Node node, Message msg) throws Exception
	{
		if(msg instanceof TrustingYou)
		{
			boolean added = ((TrustingYou)msg).trust;
			handleTrustYou(node, added);
		}
		else if(msg instanceof PersonTrustingMe)
		{
			PersonTrustingMe x = (PersonTrustingMe)msg;
			handlePersonTrustingMe(node, x);
		}
			

	}


	/**
	 * This message means: Ok you trust me, so here's a list of person who also trust me, so
	 * you can connect to them.
	 * @param node
	 * @param x
	 * @throws Exception
	 */
	private void handlePersonTrustingMe(Node node, PersonTrustingMe x) throws Exception
	{
		// this message should be received only by trusted peers!
		Person p = core.getPeoples().get(node.getHash());
		if (p.status != FriendStatus.TRUSTED)
		{
			core.getGui().pass(new Log("Received PersonTrustingMe from non-trusted "+p.getFullGid() ));
			return;
		}
		
		if(x.trust)
		{
			// we received a person that is trusting "node".
			net.add(node, x.hash);
			
			Person person = core.getPeoples().get(x.hash);
			if(person != null)
			{
				if(person.status == FriendStatus.STRANGER)
				{
					person.status = FriendStatus.FOAF;
				}
			}
			else
			{
				person = new Person();
				
				person.hash = x.hash;
				person.nickname = x.nickname;
				person.status = FriendStatus.FOAF;
				person.onlineStatus = OnlineStatus.OFFLINE;
				person.port = x.port;
				person.host = x.host;
				core.getPeoples().add(person);
			}
			core.getPeoples().save();
			PeopleManagerPlugin.updatePerson(core.getGui(), person);
			
			if(core.find(x.hash) == null) // not already connected
			{
				core.connect(x.host, x.port, person);
			}
		}
		else
		{
			// a person has removed his trust from "node"
			
			// remove from the network
			boolean delendum = net.remove(node, x.hash);
			
			// we should disconnect from this if it is not in list by someone other
			if(delendum) // this tells if it is still FOAF (becouse of some other trusted intermediary) or not
			{
				// we have also to change status from FOAF to STRANGER
				Person person = core.getPeoples().get(x.hash);
				if(person != null)
				{
					if(person.status == FriendStatus.FOAF)
					{
						person.status = FriendStatus.STRANGER;
						PeopleManagerPlugin.updatePerson(core.getGui(), person);
					}
				}
				// if it's connected but not FOAF then disconnect
				Node byebye = core.find(x.hash);
				if(byebye!=null)
				{
					byebye.close();
				}
			}
		}
		
	}

	public boolean isConnectable(String hash)
	{
		return net.isConnectable(hash);
	}
	
	private void toggleTrust(String hash, Peoples ps) throws Exception
	{
		Person p = ps.get(hash);
		if (p != null)
		{
		    if (p.status == FriendStatus.FRIEND)
		    {
		    	entrust(hash, ps, p);
		    }
		    else if (p.status == FriendStatus.TRUSTED)
		    {
		    	distrust(hash, ps, p);
		    }
		    else
		    {
		    	core.getGui().pass(new Log("Not friend, can't toggle trust"));
		    }
		}
		else
		{
			core.getGui().pass(new Log("Unknown person"));
		}
	}



	private void distrust(String hash, Peoples ps, Person p) throws Exception
	{
		p.status = FriendStatus.FRIEND; 
		ps.save();
		PeopleManagerPlugin.updatePerson(core.getGui(), p);
		
		// notify our distrust
		Node n = core.find(hash);
		if(n != null)
		{
			n.enqueue(new TrustingYou(false));
		}
		// remove all nodes that we where connected just by trust of this one
		deFoafPersons(n);
	}



	/**
	 * A trusted node has gone offline or has been untrusted.
	 * We must remove all person we foaf'ed becouse of him.
	 * @param untrustedOrDisconnectedNode
	 */
	private void deFoafPersons(Node untrustedOrDisconnectedNode)
	{
		Set<String> delendum = net.removeTrusted(untrustedOrDisconnectedNode);
		if(delendum == null) return;
		
		for (String h : delendum)
		{
			boolean foaf = net.isConnectable(h);
			if(foaf)
			{
				// still foaf, no problem
			}
			else
			{
				// was foaf, now is not, disconnect!
				Person person = core.getPeoples().get(h);
				
				
				if(person.status == FriendStatus.FOAF)
				{
					person.status = FriendStatus.STRANGER;
					PeopleManagerPlugin.updatePerson(core.getGui(), person);
					Node byebye = core.find(h);
					if(byebye!=null)
					{
						byebye.close();
					}
				}
			}
		}
	}



	private void entrust(String hash, Peoples ps, Person p) throws Exception
	{
		p.status = FriendStatus.TRUSTED;
		ps.save();
		PeopleManagerPlugin.updatePerson(core.getGui(), p);
		
		Node n = core.find(hash);
		if(n != null)
		{
			n.enqueue(new TrustingYou(true));
		}
	}
	

	private void handleTrustYou(Node node, boolean added)
	{
		if(added)
		{
			if (peopleTrustingMe.contains(node))
			{
				// already in
				return;
			}
			Person p = core.getPeoples().get(node.getHash());
			for (Node n : peopleTrustingMe)
			{
				// tell other people trusting me of this new node
				n.enqueue(new PersonTrustingMe(true, p.hash, p.nickname, p.host, p.port));
				
				// tell this new node of other people trusting me
				Person px = core.getPeoples().get(n.getHash());
				node.enqueue(new PersonTrustingMe(true, px.hash, px.nickname, px.host, px.port));
			}
			peopleTrustingMe.add(node);
		}
		else
		{
			if (!peopleTrustingMe.contains(node))
			{
				// already out
				return;
			}
			peopleTrustingMe.remove(node);
			for (Node n : peopleTrustingMe)
			{
				Person p = core.getPeoples().get(node.getHash());
				n.enqueue(new PersonTrustingMe(false, p.hash, p.nickname, p.host, p.port));
			}
			
		}
	}

	@Override
	public void nodeConnected(Node node) throws Exception
	{
		Person p = core.getPeoples().get(node.getHash());
		if (p.status == FriendStatus.TRUSTED)
		{
			node.enqueue(new TrustingYou(true));
		}

	}

	@Override
	public void nodeDisconnected(Node node) throws Exception
	{
		peopleTrustingMe.remove(node);
		deFoafPersons(node);
	}

	@Override
	public void start() throws Exception
	{
		

	}

	@Override
	public void taskTerminated(Task task, boolean interrupted) throws Exception
	{
		

	}

}
